/*
 * XQueryEditor.java
 */

package xq.ide;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.text.MessageFormat;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.text.EditorKit;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import org.apache.log4j.Logger;
import xq.api.XQRequest;
import xq.api.XQResponse;
import xq.api.XQVariable;
import xq.api.XQuery;
import xq.ide.forms.ParameterDialog;
import xq.swing.text.XQueryEditorKit;
import xq.ide.node.DataNode;
import xq.swing.Editor;
import xq.util.resource.TextResource;
import static xq.util.CommonUtil.empty;
import static xq.util.LoggerUtil.getClassLogger;

/**
 *
 * @author ThorntonRP
 */
public class XQueryEditor extends Editor {

	private static final long serialVersionUID = -3086180620321061793L;
	
	private static final Logger logger = getClassLogger();
	
	private JLabel connectionLabel;
	private JLabel unmarshalToLabel;
	private JLabel durationLabel;
	private JComboBox connectionInfoList;
	private JComboBox rootElementList;
	private JButton executeButton;
	
	private ConnectionComboBoxModel connectionInfoListModel;
	private XmlRootElementListModel rootElementListModel;
	private DefaultTreeModel elementTreeModel;
	
	private Map<String, String> lastInput = new LinkedHashMap<String, String>();

	public XQueryEditor() {
		super("text/xquery");
		EditorKit editorKit = new XQueryEditorKit();
		getTextPane().setEditorKitForContentType(editorKit.getContentType(), editorKit);
		initComponents();
	}

	public XQueryEditor(File file) throws IOException {
		super("text/xquery", file);
		EditorKit editorKit = new XQueryEditorKit();
		getTextPane().setEditorKitForContentType(editorKit.getContentType(), editorKit);
		initComponents();
	}

	/*--------------------------------------------------------------------
	 * Public Fields
	 *--------------------------------------------------------------------*/

	public ConnectionInfo getSelectedConnectionInfo() {
		return connectionInfoListModel.getSelectedItem();
	}

	public void setSelectedConnectionInfo(ConnectionInfo connectionInfo) {
		connectionInfoListModel.setSelectedItem(connectionInfo);
	}

	public TreeModel getXmlResultTreeModel() {
		return elementTreeModel;
	}

	/*--------------------------------------------------------------------
	 * Private Operations
	 *--------------------------------------------------------------------*/
	
	private Map<String, String> getParameters(XQuery xquery) {
		Set<XQVariable> externalVars = xquery.getExternalVariables();
		Map<String, String> varInput = new LinkedHashMap<String, String>(externalVars.size());
		for (XQVariable variable : externalVars) {
			String key = variable.toString();
			varInput.put(key, lastInput.get(key));
		}
		return varInput;
	}

	private XQRequest createRequest(XQuery query, Map<String, String> parameters) {
		XQRequest request = new XQRequest(query);
		for (Map.Entry<String, String> entry : parameters.entrySet()) {
			String name = parseName(entry.getKey());
			String value = entry.getValue();
			request.setVariableInput(name, value);
		}
		return request;
	}

	private String parseName(String key) {
		int index = key.indexOf(" as ");
		if (index >= 0) {
			return key.substring(1, index);
		} else {
			return key.substring(1);
		}
	}

	private void initComponents() {
		connectionLabel = new JLabel("Connection:");
		unmarshalToLabel = new JLabel("Unmarshal to:");
		durationLabel = new JLabel("");

		connectionInfoListModel = new ConnectionComboBoxModel();
		connectionInfoList = new JComboBox(connectionInfoListModel);
		//connectionInfoList.setPreferredSize(new Dimension(200, connectionInfoList.getPreferredSize().height));

		rootElementListModel = new XmlRootElementListModel();
		rootElementList = new JComboBox(rootElementListModel);
		//rootElementList.setPreferredSize(new Dimension(200, rootElementList.getPreferredSize().height));

		executeButton = new JButton("Execute");
		executeButton.addActionListener(new ExecuteActionHandler());

		getToolPanel().add(connectionLabel);
		getToolPanel().add(connectionInfoList);
		getToolPanel().add(unmarshalToLabel);
		getToolPanel().add(rootElementList);
		getToolPanel().add(executeButton);
		getToolPanel().add(durationLabel);
	}

	/*--------------------------------------------------------------------
	 * Private Classes
	 *--------------------------------------------------------------------*/
	
	private class XQueryRunner extends Thread {

		@Override
		public void run() {
			try {
				Application.getInstance().getMainFrame().getOutputHandler().printInfo("");
				XQuery query;
				if (getFile() == null) {
					query = new XQuery(getText(), XQOptions.getInstance().getResourceClassLoader());
				} else {
					TextResource resource = new TextResource(getFile(), "text/xquery");
					query = new XQuery(resource, XQOptions.getInstance().getResourceClassLoader());
				}
				ConnectionInfo connInfo = connectionInfoListModel.getSelectedItem();
				Map<String, String> parameters = getParameters(query);
				int option = Application.getInstance().getMainFrame().promptForParameters(parameters);
				if (option == ParameterDialog.APPROVE_OPTION) {
					durationLabel.setText("Running...");
					durationLabel.setForeground(Color.GREEN.darker());
					lastInput = parameters;
					XQRequest request = createRequest(query, parameters);
					XQResponse<String> response = XQUtil.executeQuery(connInfo, request);
					double duration = response.getDuration() / 1000.0;
					durationLabel.setText(MessageFormat.format("{0,number,#.###} seconds", duration));
					durationLabel.setForeground(Color.BLUE);
					if (!empty(rootElementListModel.getSelectedItem())) {
						try {
							ClassLoader loader = XQOptions.getInstance().getBindingClassLoader();
							Class c = loader.loadClass(rootElementListModel.getSelectedItem());
							JAXBContext ctx = JAXBContext.newInstance(c);
							Unmarshaller um = ctx.createUnmarshaller();
							Object rootElement = um.unmarshal(new StringReader(response.getResult()));
							if (rootElement == null) {
								elementTreeModel = null;
							} else {
								elementTreeModel = new DefaultTreeModel(new DataNode(rootElement));
							}
						} catch (Throwable thrown) {
							logger.warn(thrown.toString(), thrown);
						}
					}
					Application.getInstance().getMainFrame().getOutputHandler().printResponse(request, response);
				} else {
					durationLabel.setText("");
					durationLabel.setForeground(Color.BLACK);
				}
			} catch (Exception ex) {
				durationLabel.setText("Query failed.");
				durationLabel.setForeground(Color.RED);
				Application.getInstance().getMainFrame().getOutputHandler().printException(ex);
			}
		}
	}

	private class ExecuteActionHandler implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			if (getFile() != null) {
				try {
					getTextPane().write(new FileWriter(getFile()));
				} catch (IOException ex) {
					logger.warn("Failed to save " + getFile().getPath(), ex);
				}
			}
			new XQueryRunner().start();
		}
	}
	
	private class ConnectionChangeHandler implements PropertyChangeListener {

		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			connectionInfoListModel.removeAllElements();
			for (ConnectionInfo connectionInfo : XQOptions.getInstance().getConnectionPreferences()) {
				connectionInfoListModel.addElement(connectionInfo);
			}
		}
	}

	private class BindingClasspathChangeHandler implements PropertyChangeListener {

		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			rootElementListModel.removeAllElements();
			rootElementListModel.addElement("");
			for (String className : XQOptions.getInstance().getRootElementClassNames()) {
				rootElementListModel.addElement(className);
			}
		}
	}

	private class ConnectionComboBoxModel extends DefaultComboBoxModel {

		private static final long serialVersionUID = 1L;

		public ConnectionComboBoxModel() {
			for (ConnectionInfo connectionInfo : XQOptions.getInstance().getConnectionPreferences()) {
				addElement(connectionInfo);
			}
			XQOptions.getInstance().addPropertyChangeListener("connectionPreferences", new ConnectionChangeHandler());
		}

		@Override
		public ConnectionInfo getSelectedItem() {
			return (ConnectionInfo) super.getSelectedItem();
		}
	}

	private class XmlRootElementListModel extends DefaultComboBoxModel {

		private static final long serialVersionUID = 1L;

		public XmlRootElementListModel() {
			addElement("");
			for (String className : XQOptions.getInstance().getRootElementClassNames()) {
				addElement(className);
			}
			XQOptions.getInstance().addPropertyChangeListener("bindingClasspath", new BindingClasspathChangeHandler());
		}

		@Override
		public String getSelectedItem() {
			return (String) super.getSelectedItem();
		}
	}
}
